import { isObject } from '../utils.js';
import { myArrayPrototype } from './array.js';
import Dep from './dep.js';

function dependArray(arr) {
  for (const item of arr) {
    if (item.__ob__) {
      item.__ob__.dep.depend();
    }
    if (Array.isArray(item)) {
      dependArray(item);
    }
  }
}

// window.deps = [];

// key => 'b'
// val => { b1: 2, b2: 3 }
export function defineReactive(data, key, val, cb) {
  // 对于data中的每一个key，都会有一个闭包的val

  // 这个dep专门用来收集当前data中的这个key的依赖（闭包）
  const dep = new Dep();

  // window.deps.push(dep);

  // val有可能又是一个对象，所以，需要做递归处理
  // childOb 是 val.__ob__
  const childOb = observe(val);

  Object.defineProperty(data, key, {
    // 如果不对描述符做配置，configurable、enumerable、writable默认是false

    // 公共描述符（共享的）
    configurable: true,
    enumerable: true,

    // 数据描述符（和存取描述符冲突）
    // writable,
    // value

    // 知识储备：
    // 1. 对象中的每一个键，都有一个闭包的dep对象，用来收集自己的watcher

    // 当读取b属性时触发
    // 这个b是对象，如果b内部的属性发生变化（vm.b.b1 = 22），
    // b这个值，本身没变，还是原来那个对象的内存地址，所以就不会触发b的setter
    // 就不会触发b的dep的notify，侦听b对象的所有Watcher，就不会收到通知
    // 严格意义上来讲，需不需要触发侦听b对象的Watcher？
    // 答案是需要，内部的属性变量，本质上b也变了，所以应该通知b的所有Watcher
    // 那么 ==> 就需要在b的子属性的dep中收集b的Watcher
    // 因为 vm.b.b1 = 22 => 会触发b1的setter，就会触发b1的dep的notify
    // 但是，又不能收集到每一个键的dep中，性能不好，如果能搞一个统一的地方就好了
    // 统一的地方，就是对象本身的__ob__上的dep
    // 所以，这样，就能在b的子属性触发setter的时候，拿到所有b的Wacher
    // 但还是需要事先收集好
    // 存取描述符（和数据描述符冲突）
    get() {
      console.log(`你正在获取 ${key} 的值：${val}`);

      // 当有东西使用我这个数据时，就说明依赖了我，就要将这个依赖收集起来
      dep.depend();

      if (childOb) {
        // 将当前watcher收集到当前对象的__ob__中，
        // 做到当对象内部的属性发生变化时，通知对象的watcher
        childOb.dep.depend();

        if (Array.isArray(val)) {
          dependArray(val);
        }
      }

      console.log(`${key} - subs:`, dep.subs);

      // 借助闭包
      return val;
    },
    set(newValue) {
      console.log(`你正在给 ${key} 赋值：${newValue}`);

      // 如果赋值时，新值和旧值一样，那么就不需要触发更新通知
      if (newValue === val) return;

      // 这个赋值的newValue，有可能是一个新对象，所以也需要递归处理响应式
      observe(newValue);

      const oldValue = val;

      // data[key] = newValue // 死循环
      // 借助闭包
      val = newValue;
      cb && cb(newValue, oldValue);

      // 当数据发生变化时，通过收集好的所有依赖，将最新的值传过去
      dep.notify();
      // data.__ob__.dep.notify(newValue, oldValue);
    }
  });
}

/**
 * 观察者
 */
class Observer {
  dep;

  constructor(data) {
    this.dep = new Dep();

    Object.defineProperty(data, '__ob__', {
      // 默认就是false
      // enumerable: false
      value: this
    });

    if (Array.isArray(data)) {
      // 要去修改data的原型，修改成自己的数组原型
      Object.setPrototypeOf(data, myArrayPrototype);
      this.observeArray(data);
    } else {
      this.walk(data);
    }
  }

  // 走你
  walk(data) {
    for (const [key, value] of Object.entries(data)) {
      defineReactive(data, key, value);
    }
  }

  observeArray(data) {
    for (const value of data) {
      // 数组中的元素有可能又是对象，所以需要循环做响应式处理
      observe(value);
    }
  }
}

export function observe(data) {
  // typeof null、typeof 数组 ==> object
  // if (typeof data !== 'object' || data === null) return;
  if (!isObject(data)) return;

  // 避免给同一个对象，重复做响应式处理
  if (typeof data.__ob__ === 'undefined') {
    // 一旦new过了，data身上就有__ob__了
    return new Observer(data);
  }

  return data.__ob__;
}
